Framework.Entities = {}

local OwnersList = {}

local _InternalSetOwner
local MapEntitiesSpawned = false

local function _InternalGetOwnerID( Owner )
	local Idx = ""
	if( (type(Owner) == "Entity" && Owner:GetClass() == "worldspawn") || type(Owner) == "Player" ) then	
		Idx = "PlayerObject"
	elseif( Framework.Utilities.IsUniqueID( Owner ) ) then
		Idx = "UniqueID"
	elseif( Framework.Utilities.IsSteamID( Owner ) ) then
		Idx = "SteamID"
	elseif( SERVER && Framework.Utilities.IsIP( Owner ) ) then
		Idx = "IPAddress"
	else
		Framework.IO.Error( "Couldn't extract owner ID from '" .. tostring(Owner) .. "', unknown type" )
		return false
	end
	for k, v in pairs( OwnersList ) do
		if( v[Idx] == Owner ) then
			return k
		end
	end
	return false
end
	
function Framework.Entities.GetByOwner( PL_ET_SI_IP_UN_Owner )
	local ID = _InternalGetOwnerID( PL_ET_SI_IP_UN_Owner )
	if( ID ) then
		return OwnersList[ID].Entities
	end
end	

function Framework.Entities.GetOwner( ET_Entity )
	return ET_Entity._FOwner && OwnersList[ET_Entity._FOwner].PlayerObject or false
end

function Framework.Entities.GetMapEntities()
	return OwnersList[0].Entities
end

function Framework.Entities.HaveEntitiesSpawned()
	return MapEntitiesSpawned
end

if( SERVER ) then

	function _InternalSetOwner( Entity, pl )
		local OldOwner = Framework.Entities.GetOwner( Entity )
		if( OldOwner ) then
			if( pl && pl == OldOwner ) then
				return true
			end
			local Table = OwnersList[Entity._FOwner].Entities
			for k, v in pairs( Table ) do
				if( v == Entity ) then
					table.remove( Table, k )
					break
				end
			end
			
			if( #Table == 0 && !OldOwner:IsValid() ) then
				OwnersList[Entity._FOwner] = nil
			end			
			Entity._FOwner = nil
		end
		if( pl ) then
			table.insert( Framework.Entities.GetByOwner( pl ), Entity )
			Entity._FOwner = _InternalGetOwnerID( pl )
		end
		timer.Simple( 0.5, function()
			if( !Entity ) then return end
			umsg.Start( "Framework.Entities.UpdateEntityOwnership" )
				umsg.Entity( Entity )
				umsg.Short( Entity._FOwner )
			umsg.End()
		end )
	end
	
	local function _InternalSpawnedEntity( pl, Entity )
		_InternalSetOwner( Entity, pl )
	end
	
	hook.Add( "PlayerSpawnedSENT", "Framework.Entities.PlayerSpawnedSENT", _InternalSpawnedEntity )
	hook.Add( "PlayerSpawnedNPC", "Framework.Entities.PlayerSpawnedNPC", _InternalSpawnedEntity )
	hook.Add( "PlayerSpawnedVehicle", "Framework.Entities.PlayerSpawnedVehicle", _InternalSpawnedEntity )
	
	hook.Add( "PlayerSpawnedProp", "Framework.Entities.PlayerSpawnedProp", function(a,_,b) _InternalSpawnedEntity(a,b) end )
	hook.Add( "PlayerSpawnedEffect", "Framework.Entities.PlayerSpawnedEffect", function(a,_,b) _InternalSpawnedEntity(a,b) end )
	hook.Add( "PlayerSpawnedRagdoll", "Framework.Entities.PlayerSpawnedRagdoll", function(a,_,b) _InternalSpawnedEntity(a,b) end )
	
	hook.Add( "PlayerInitialSpawn", "Framework.Entities.PlayerSpawned", function(pl) timer.Simple( 0.5, function(pl) 
		local OwnerIndex = 0
		for k, v in pairs( OwnersList ) do
			if( v.UniqueID == pl:UniqueID() ) then
				v.PlayerObject = pl
				v.IPAddress = pl:IPAddress()
				OwnerIndex = k
				local Rp = RecipientFilter()
				for _, p in pairs(player.GetAll())do if( p != pl ) then Rp:AddPlayer(p) end end
				umsg.Start( "Framework.Entities.UpdatePlayer", Rp )
					umsg.Short( OwnerIndex )
					umsg.Entity( pl )
				umsg.End()
				
				break
			end
		end
		
		if( OwnerIndex == 0 ) then
			OwnerIndex = table.insert( OwnersList, { 
				UniqueID 	 = pl:UniqueID(),
				SteamID  	 = pl:SteamID(),
				IPAddress 	 = pl:IPAddress(),
				PlayerObject = pl,
				Entities 	 = {}
			} )
			umsg.Start( "Framework.Entities.AddPlayer" )
				umsg.Short( OwnerIndex )
				umsg.Entity( pl )
				umsg.Long( pl:UniqueID() )
				umsg.String( pl:SteamID() )
			umsg.End()
		else
			local Rp = RecipientFilter()
			for _, p in pairs(player.GetAll())do if( p != pl ) then Rp:AddPlayer(p) end end
			umsg.Start( "Framework.Entities.UpdatePlayer", Rp )
				umsg.Short( OwnerIndex )
				umsg.Entity( pl )
			umsg.End()
		end
		for k, v in pairs( OwnersList ) do
			umsg.Start( "Framework.Entities.AddPlayer", pl )
				umsg.Short( k )
				umsg.Entity( v.PlayerObject )
				umsg.Long( v.UniqueID )
				umsg.String( v.SteamID )
			umsg.End()
			
			timer.Simple( 1, function( pl )
				local EntCount =#v.Entities
				if( EntCount > 0 ) then
					umsg.Start( "Framework.Entities.AddEntity", pl )
						umsg.Short( k )
						umsg.Short( math.min( EntCount, 50 ) )
						for i, Ent in pairs( v.Entities ) do
							if( i % 50 == 0 ) then
								umsg.End()
								umsg.Start( "Framework.Entities.AddEntity", pl )
								umsg.Short( k )
								umsg.Short( math.min( EntCount-i, 50 ) )
							end
							umsg.Entity( Ent )
						end
					umsg.End()
				end
			end, pl )
		end
	end, pl ) end )
	
	function Framework.Entities.SetOwner( ET_Entity, PL_Player )
		return _InternalSetOwner( ET_Entity, PL_Player )
	end
	
	function Framework.Entities.RemoveByOwner( PL_Player )
		local ID = _InternalOwnerGetID( PL_Player )
		if( ID ) then
			for k, v in pairs( OwnersList[ID].Entities ) do
				v:Remove()
			end
		end
	end
	
	function Framework.Entities.RemoveByClass( ST_Class )
		for k, v in pairs( ents.GetAll() ) do
			if( v:GetClass():lower():find( "^" .. ST_Class .. "$" ) ) then
				v:Remove()
			end
		end
	end
else	
	function _InternalSetOwner( Entity, Owner )
		if( !Entity || !Entity:IsValid() ) then return end 
		
		if( Entity._FOwner && Entity._FOwner != Owner ) then
			for k, v in pairs( OwnersList[Entity._FOwner].Entities ) do
				if( v == Entity ) then
					table.remove( OwnersList[Entity._FOwner].Entities, k )
					
					if( #OwnersList[Entity._FOwner].Entities == 0 && !OwnersList[Entity._FOwner].PlayerObject:IsValid() ) then
						OwnersList[Entity._FOwner] = nil
					end	
					
					break
				end
			end
		end
		if( Owner ) then
			table.insert( OwnersList[Owner].Entities, Entity )
		end
		Entity._FOwner = Owner
	end
	
	usermessage.Hook( "Framework.Entities.AddEntity", function( um )
		local Owner = um:ReadShort()
		for i = 1, um:ReadShort() do
			local Entity = um:ReadEntity()
			if( Entity ) then
				_InternalSetOwner( Entity, Owner )
			end
		end
	end )
	
	usermessage.Hook( "Framework.Entities.UpdateEntityOwnership", function( um )
		local Entity = um:ReadEntity()
		local Owner  = um:ReadShort()
		_InternalSetOwner( Entity, Owner )
	end )

	usermessage.Hook( "Framework.Entities.AddPlayer", function( um ) 
		local OwnerIndex = um:ReadShort()
		OwnersList[OwnerIndex] = {
			PlayerObject = um:ReadEntity(),
			UniqueID     = um:ReadLong(),
			SteamID      = um:ReadString(),
			Entities	 = {}
		}
	end )
	
	usermessage.Hook( "Framework.Entities.UpdatePlayer", function( um ) 
		local OwnerIndex = um:ReadShort()
		OwnersList[OwnerIndex].PlayerObject = um:ReadEntity()
	end )
end

hook.Add( "EntityRemoved", "Framework.Entities.EntityRemoved", function( Entity )
	if( Entity && Entity:IsValid() ) then
		if( Entity:GetClass():lower() == "player" ) then
			for k, v in pairs( OwnersList ) do
				if( v.PlayerObject == Entity ) then
					if( #v.Entities == 0 ) then
						OwnersList[k] = nil
						break
					end
				end
			end
		else
			_InternalSetOwner( Entity )
		end
	end
end )

hook.Add( "InitPostEntity", "Framework.Entities.RegisterWorldEntities", function()
	local WorldSpawn = ents.FindByClass( "worldspawn" )[1]
	-- Shh, don't tell anyone I did this!
	WorldSpawn.GetName = function()return "World" end
	WorldSpawn.Nick = WorldSpawn.GetName
	
	if( SERVER ) then
		OwnersList[0] = { 
			PlayerObject = WorldSpawn,
			IPAddress = "0.0.0.0:0",
			SteamID = "STEAM_0:0:0",
			UniqueID = 0,
			Name = "World",
			Entities = {}
		} 
		
		for k, v in pairs( ents.GetAll() ) do
			if( v.GetPhysicsObject && v:GetPhysicsObject():IsValid() ) then
				v._FOwner = 0
				table.insert( OwnersList[0].Entities, v )
			end
		end
	end
	MapEntitiesSpawned = true
end )